
/**
 * @name MassWG (MASS Wind Gradient 1) Calculates surface moisture flux for
 * January 2-31, 1980 Part of Pacific Northwest Climate Data Analysis
 *
 * @version 0.1.505 Alpha, MASS Wind Gradient 2, 5th commit
 * @date 10/10/2013
 * @author Niko Simonson
 * @author Brett Yasutake
 */

// IMPORTS
import MASS.*;                  // MASS Library
import java.io.IOException;     // IO Exceptions
import java.util.*;             // Vectors, etc.
import java.util.AbstractMap.*; // for simple key-value pairs
import java.util.logging.Level;
import java.util.logging.Logger;
import ucar.ma2.ArrayFloat;     // for netCDF reader
import ucar.ma2.IndexIterator;  // for netCDF reader
import ucar.nc2.NetcdfFile;     // for netCDF reader
import ucar.nc2.Variable;       // for netCDF reader

public class MassWG2 extends Place {

    /**
     * CONSTANTS *
     */
    // MASS arguments
    private static final String[] MASSARGS = {"dslab", "dslab-302",
        "machinefile.txt", "12345"};            // connection arguments
    private static String[] massArgs;
    private static final int NPROCESSES = 2;    // number of processes to employ
    private static int nProcesses;
    private static final int NTHREADS = 2;      // number of threads to employ
    private static int nThreads;
    private static final int HANDLE = 0;        // unique Places handle number
    // Functions
    private static final int init_ = 0;         // initialize Places
    private static final int readWinds_ = 1;
    private static final int computeWG_ = 2;    // compute regional avg temps
    private static final int storeWGResults_ = 3;   // exchange regional temps
    // Wind gradient constants
    private static final int DAYS = 30;         // days of data collection
    private static final int DAYSEGMENTS = 4;   // time slices per day
    private static int fileRange;
    private static final int DISTANCE = 30;     // km distance between two geographic cells
    // Target variable names
    private static final String XWIND = "U10";  // east-west windspeed at 10m above surface
    private static final String YWIND = "V10";  // north-south windspeed at 10m above surface
    // Northwest regional climate data sample
    private static final String FILENAMEBEGIN = "/home/brett/netcdf_files/1980_jan/wrfout_d02_1980-01-";
    private static final String FILENAMEEND = ":00:00"; // end of file name
    private static final int XRANGE = 123;              // width of data set (# of grid cells)
    private static final int YRANGE = 162;              // length of data set (# of grid cells)
    private static final int METADATALENGTH = 7;        // # of metadata elements
    /**
     * VARIABLES *
     */
    private int myX;                                // time slices in a day
    private int myY;                                // days
    private static double[][] xSpd;                        // E-W wind speed
    private static double[][] ySpd;                        // N-S wind speed
    private double[][] xg;                          // E-W wind gradient
    private double[][] yg;                          // N-S wind gradient
    private static Places computeWindGradient = null;   // MASS parallel environment
    private static ResultsAdapter[][] ra;           // Stores results
    private static ProvenanceAdapter pa;            // Stores provenance
    private static SimpleEntry<String, Integer[]> entryXGradient; // x gradient result entry
    private static SimpleEntry<String, Integer[]> entryYGradient; // y gradient result entry
    private static String[][] resultsName;          // Result files' names
    
    private static String[] fileNames;

    /**
     * @name MassWG1
     * @param interval unused Empty constructor required for MASS Place
     * implementation
     */
    public MassWG2(Object interval) {
        // A constructor implementation is required for MASS Place
        xSpd = new double[XRANGE][YRANGE];
        ySpd = new double[XRANGE][YRANGE];
        xg = new double[XRANGE - 2][YRANGE - 2];
        yg = new double[XRANGE - 2][YRANGE - 2];
    }

    /**
     * @name callMethod Provides enumerated list of functions to be parallelized
     * in MASS
     *
     * @param funcId - function ID (listed as Constants)
     * @param args - function parameters
     * @return - unused
     */
    public Object callMethod(int funcId, Object args) {
        switch (funcId) {
            case init_:
                return init(args);
            case readWinds_:
                return readWinds(args);
            case computeWG_:
                return computeWG(args);
            case storeWGResults_:
                return storeWGResults(args);
        }
        return null;
    }

    /**
     * @name init Initialization information for MASS Places index[0]: days
     * index[1]: time slices in a day
     *
     * @param args MASS initialization arguments, # of processes, # of threads
     * @return unused
     */
    public Object init(Object args) {
        // Each place keeps track of its time position
        myX = index[1];
        myY = index[0];

        // Name the results files
        resultsName[myX][myY] = "WindGradient" + myY + "-" + myX;

        // Instantiate the results adapters
        ra[myX][myY] = new ResultsAdapter(ResultsAdapter.DOUBLE, resultsName[myX][myY], entryXGradient, entryYGradient);

        return (Object) null;
    }

    /**
     * @name readWinds Read wind magnitudes and directions from NetCDF file
     * @param args
     * @return
     */
    public Object readWinds(Object args) {
        xSpd = InputAdapter.read2DVariable(fileNames[(myY*DAYSEGMENTS)+myX], XWIND, XRANGE, YRANGE);

        ySpd = InputAdapter.read2DVariable(fileNames[(myY*DAYSEGMENTS)+myX], YWIND, XRANGE, YRANGE);
        
        if (xSpd == null) System.err.println("Null input adapter return");
        
        return (Object) null;

    }

    /**
     * @name computeMF Open netCDF file and compute wind gradient over the
     * entire region
     *
     * @param args unused
     * @return unused
     */
    public Object computeWG(Object args) {
        // Clip the boundary values. Coordinates are offset by 1
        for (int x = 0; x < XRANGE - 2; ++x) {
            for (int y = 0; y < YRANGE - 2; ++y) {
                xg[x][y] = (xSpd[x + 2][y + 1] - xSpd[x][y + 1]) / DISTANCE;
                yg[x][y] = ((ySpd[x + 1][y + 2] - ySpd[x + 1][y])) / DISTANCE;
            }
        }
        return (Object) null;
    }

    /**
     * @name getFileName Private helper function to dynamically generate correct
     * file name
     *
     * @param thisX daily time slice
     * @param thisY day
     *
     * @return
     */
    private String getFileName(int thisX, int thisY) {
        // VARIABLES
        String strX, strY;  // file component numbers

        // calculate correct day of the month
        strY = Integer.toString(thisY + 2);

        // calculate correct time of the day
        strX = Integer.toString(thisX * 6);

        // adjust for numbers less than 10
        if (Integer.parseInt(strX) < 10) {
            strX = "0" + strX;
        }
        if (Integer.parseInt(strY) < 10) {
            strY = "0" + strY;
        }
        // return proper file name
        return FILENAMEBEGIN + strY + "_" + strX + FILENAMEEND;
    }

    /**
     * @name storeWGResults Record results to the results adapter
     * @param args
     * @return unused
     */
    public Object storeWGResults(Object args) {
        ra[myX][myY].log2DVar("xGradient", xg);
        ra[myX][myY].log2DVar("yGradient", yg);

        return null;
    }

    public static void initialize() {
        try {
            int count = DAYS * DAYSEGMENTS;     // total number of files
            Object[] fixer = new Object[count];
            ra = new ResultsAdapter[DAYSEGMENTS][DAYS];
            Integer[] dimensions = new Integer[2];
            dimensions[0] = XRANGE - 2;
            dimensions[1] = YRANGE - 2;
            entryXGradient = new SimpleEntry<String, Integer[]>("xGradient", dimensions);
            entryYGradient = new SimpleEntry<String, Integer[]>("yGradient", dimensions);
            resultsName = new String[DAYSEGMENTS][DAYS];
            
            MASS.init(MASSARGS, NPROCESSES, NTHREADS);
            computeWindGradient = new Places(HANDLE, "MassWG2", null,
                            DAYS, DAYSEGMENTS);
            computeWindGradient.callAll(init_, fixer);
        } catch (Exception ex) {
            Logger.getLogger(MassWG2.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    public static void setFiles(String[] fileArray) {
        fileNames = fileArray;
    }
    
    public static void initialize(String[] mass, int processes, int threads, int numberOfFiles) {
        try {
            fileRange = numberOfFiles;
            massArgs = mass;
            nProcesses = processes;
            nThreads = threads;
            
            Object[] fixer = new Object[fileRange];
            ra = new ResultsAdapter[DAYSEGMENTS][DAYS];
            Integer[] dimensions = new Integer[2];
            dimensions[0] = XRANGE - 2;
            dimensions[1] = YRANGE - 2;
            entryXGradient = new SimpleEntry<String, Integer[]>("xGradient", dimensions);
            entryYGradient = new SimpleEntry<String, Integer[]>("yGradient", dimensions);
            resultsName = new String[DAYSEGMENTS][DAYS];

            MASS.init(massArgs, nProcesses, nThreads);
            computeWindGradient = new Places(HANDLE, "MassWG2", null,
                    DAYS, DAYSEGMENTS);
            computeWindGradient.callAll(init_, fixer);
        } catch (Exception ex) {
            Logger.getLogger(MassWG2.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    /**
     * @name runAnalysis Performs entire analysis, manages MASS
     *
     * @param status StatusModel UI view
     * @param initialized set to true if MASS has already been initialized
     * @throws Exception
     */
    public static void runAnalysis(StatusModel status, boolean initialized) throws Exception {
// TODO create an initialization function to de couple inintialization and running
// the analysis
        initialized = true;

        // VARIABLES
        long start, end, totalTime;         // data provenance for execution time
        int count = DAYS * DAYSEGMENTS;     // total number of files

        // dummy object to prevent array over- or underflow
        Object[] fixer = new Object[count];



        // initialize data structures for analysis
        if (!initialized) {
            ra = new ResultsAdapter[DAYSEGMENTS][DAYS];
            Integer[] dimensions = new Integer[2];
            dimensions[0] = XRANGE - 2;
            dimensions[1] = YRANGE - 2;
            entryXGradient = new SimpleEntry<String, Integer[]>("xGradient", dimensions);
            entryYGradient = new SimpleEntry<String, Integer[]>("yGradient", dimensions);
            resultsName = new String[DAYSEGMENTS][DAYS];
        }

        try {
            // Provenance collection
            status.reportMessage("Storing Metadata");
            SimpleEntry<String, String>[] provenanceEntries = new SimpleEntry[METADATALENGTH];
            provenanceEntries[0] = new SimpleEntry<String, String>("Controlling_Node", MASSARGS[0]);
            provenanceEntries[1] = new SimpleEntry<String, String>("Controlling_Node_Password", MASSARGS[1]);
            provenanceEntries[2] = new SimpleEntry<String, String>("Machine_Life_File_Name", MASSARGS[2]);
            provenanceEntries[3] = new SimpleEntry<String, String>("Port_Number", MASSARGS[3]);
            provenanceEntries[4] = new SimpleEntry<String, String>("No_of_Processes", String.valueOf(NPROCESSES));
            provenanceEntries[5] = new SimpleEntry<String, String>("No_of_Threads", String.valueOf(NTHREADS));
            provenanceEntries[6] = new SimpleEntry<String, String>("No_of_Nodes", String.valueOf(DAYS * DAYSEGMENTS));
            pa = new ProvenanceAdapter("WindGradientProvenance");
            pa.create(provenanceEntries);

            // start MASS
            status.reportMessage("Starting MASS");

            // begin time measurement
            start = new Date().getTime();

            // initialize MASS
            if (!initialized) {
                MASS.init(MASSARGS, NPROCESSES, NTHREADS);
            }

            // Create Places
            // Each computing node corresponds to one netCDF file
            if (!initialized) {
                computeWindGradient = new Places(HANDLE, "MassWG2", null,
                        DAYS, DAYSEGMENTS);
            }
            status.reportMessage("Computational Nodes created");

            // Initialize Places
            if (!initialized) {
                computeWindGradient.callAll(init_, fixer);
            }
            status.reportMessage("Computational Nodes initialized");

            // Read winds at each time slice
            computeWindGradient.callAll(readWinds_, fixer);
            status.reportMessage("Source values read");

            // Compute wind gradient
            computeWindGradient.callAll(computeWG_, fixer);
            status.reportMessage("Calculations completed");

            // Store results
            computeWindGradient.callAll(storeWGResults_, fixer);

            // end time measurement
            end = new Date().getTime();
            totalTime = end - start;    //milliseconds

            status.reportMessage("Results stored");

            // Report execution time
            System.err.println("Total execution time (milliseconds): "
                    + totalTime);
            status.reportMessage("Total execution time (milliseconds): "
                    + totalTime);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * @name finish Terminate and clean-up MASS by calling MASS's finish
     * function
     */
    public static void finish() {
        MASS.finish();
    }
}
